package modelo.vehiculos;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import compartido.ITransaccion;

import modelo.carrera.Conductor;
import modelo.carrera.Infraccion;
import modelo.comportamiento.Accion;
import modelo.comportamiento.AccionRecomendada;
import modelo.controles.Policia;
import modelo.escenario.Cruce;
import modelo.escenario.Recorrido;
import modelo.escenario.Ciudad;
import modelo.escenario.Transitable;
import modelo.escenario.Transitable.SentidoTransitable;
import modelo.geometria.Vector;
import modelo.geometria.Rectangulo;
import modelo.obstaculos.Obstaculo;
import modelo.seniales.Semaforo;

public abstract class Vehiculo extends Obstaculo implements ITransaccion {
	/** Metodos y atributos de clase. */
	//private static final double RAZON_DESPLAZAMIENTO_HORIZONTAL = 1/3; // Cuantas veces su ancho se desplaza horizontalmente.
	private static final double mediaRaizDeDos = Math.sqrt(2)/2;
	static private final double unidadDeTiempo = 1;
	private static final double marchaReversa = 0.1;

	static public DistanciaInterpretada interpretarDistancia(double distancia) {
		if (distancia < 5) return DistanciaInterpretada.INMEDIATA;
		if (distancia < 15) return DistanciaInterpretada.CERCANA;
		if (distancia < 50) return DistanciaInterpretada.INTERMEDIA;
		return DistanciaInterpretada.LEJANA;
	}
	
	static public VelocidadInterpretada interpretarVelocidad(double velocidad) {
		if (velocidad < 1) return VelocidadInterpretada.DETENIDO;
		if (velocidad < 30) return VelocidadInterpretada.LENTA;
		if (velocidad < 80) return VelocidadInterpretada.CRUCERO;
		return VelocidadInterpretada.RAPIDA;
	}
	
	static public DistanciaInterpretada interpretarDistanciaHorizontal(double distancia) {
		return interpretarDistancia(distancia);
	}
	
	/**
	 * @brief Mapa para resolver como tratar los giros por el auto.
	 */
	static protected Map<SentidoVehiculo,Vector> ponderacionDeAvance;
	static protected Vector getPonderacionDeAvance(SentidoVehiculo sentido) {
		if (ponderacionDeAvance == null) {
			ponderacionDeAvance = new TreeMap<SentidoVehiculo, Vector>();
			
			ponderacionDeAvance.put(SentidoVehiculo.NORTE, new Vector(0,1));
			ponderacionDeAvance.put(SentidoVehiculo.NORESTE, new Vector(mediaRaizDeDos,mediaRaizDeDos));
			ponderacionDeAvance.put(SentidoVehiculo.ESTE, new Vector(1,0));
			ponderacionDeAvance.put(SentidoVehiculo.SURESTE, new Vector(mediaRaizDeDos,-mediaRaizDeDos));
			ponderacionDeAvance.put(SentidoVehiculo.SUR, new Vector(0,-1));
			ponderacionDeAvance.put(SentidoVehiculo.SUDOESTE, new Vector(-mediaRaizDeDos,-mediaRaizDeDos));
			ponderacionDeAvance.put(SentidoVehiculo.OESTE, new Vector(-1,0));
			ponderacionDeAvance.put(SentidoVehiculo.NORESTE, new Vector(-mediaRaizDeDos,mediaRaizDeDos));
		}
		return ponderacionDeAvance.get(sentido);
	}
	
	

	/** Metodos y atributos de instancia */
	private double velocidadMaxima;
	private ArrayList<Infraccion> infraccion;
	private double velocidad;
	private Conductor conductor;
	private SentidoVehiculo sentido;
	private double ancho;
	private double largo;
	private Recorrido recorrido;
	private boolean chocado;
	private boolean llegado;
	private double aceleracion;
	private double aceleracionMaxima;
	private double frenadoMaximo;
	private Vector posicionInicial;
	private Vector posicionFinal;

	public Vehiculo(Conductor conductor, Vector posicionInicial, Vector posicionFinal,SentidoVehiculo sentido, double largo, double ancho, double velocidadActual, double velocidadMaxima, double aceleracionMaxima, double frenadoMaximo) {
		super(new Rectangulo(posicionInicial, largo, ancho));
		
		this.chocado = false;
		this.llegado = false;
		
		this.largo = largo;
		this.ancho = ancho;
		this.sentido = sentido;
		this.conductor = conductor;
		this.infraccion = new ArrayList<Infraccion>();

		this.velocidad = velocidadActual;
		this.velocidadMaxima = velocidadMaxima;
		
		this.aceleracion = 5;
		this.aceleracionMaxima = aceleracionMaxima;
		this.frenadoMaximo = frenadoMaximo;
		
		this.posicionInicial = posicionInicial;
		this.posicionFinal = posicionFinal;
	
		Cruce cruceOrigen = Ciudad.getCiudad().getCruce(posicionInicial);
		Cruce cruceDestino = Ciudad.getCiudad().getCruce(posicionFinal);
		
		if (cruceOrigen != null && cruceDestino != null) {
			
			Ciudad.getCiudad().getGuiaT().caminoMinimo(cruceOrigen, cruceDestino);	
		}

	}
	
	public void manejar(Accion a) {
		a.aplicar(this);
	}
	 
	public Conductor getConductor() {
		return this.conductor;
	}

	public double getDistancia(Obstaculo obstaculo) {
		return getPosicion().getDistancia(obstaculo.getPosicion());
	}

	public Iterable<Infraccion> getInfracciones() {
		return this.infraccion;
	}
	
	public void addInfraccion(Infraccion infraccion) {
		this.infraccion.add(infraccion);
	}
	
	public void clearInfracciones() {
		this.infraccion.clear();
	}
	
	public boolean hasInfracciones() {
		return (!this.infraccion.isEmpty());
	}

	public void setRecorrido(Recorrido recorrido) {
		this.recorrido = recorrido;
	}

	public Recorrido getRecorrido() {
		return this.recorrido;
	}

	public double getVelocidadMaxima() {
		return velocidadMaxima;
	}
	
	public double getDistancia(Vehiculo otroVehiculo) {
		return getPosicion().getDistancia(otroVehiculo.getPosicion());
	}

	protected double getVelocidadMaximaEnReversa() {
		return marchaReversa*getVelocidadMaxima();
	}
	
	protected void setVelocidad(double velocidad) {
		double velocidadNormalizada = velocidad + getVelocidadMaximaEnReversa();
		if (velocidadNormalizada <= 0)
			velocidadNormalizada = getVelocidadMaximaEnReversa();
		else if (velocidadNormalizada > getVelocidadMaximaEnReversa()+getVelocidadMaxima() )
			velocidadNormalizada = getVelocidadMaxima();
		else
			velocidadNormalizada = velocidad;
		
		this.velocidad = velocidadNormalizada;
	}

	public double getVelocidad() {
		return velocidad;
	}

	//TODO revisar estos metodos (Distancia y Velocidad Horizontal y Vertical
	public double getDistanciaVertical(Obstaculo obstaculo) {
		return getPosicion().getDistanciaY(obstaculo.getPosicion());
	}
	
	public double getDistanciaHorizontal(Obstaculo obstaculo) {
		return getPosicion().getDistanciaX(obstaculo.getPosicion());
	}
	
	public boolean girarIzquierda() {
		return true ; //TODO girar Pi/4
	}
	
	public boolean girarDerecha() {
		return true; // TODO girar -Pi/4
	}

	public boolean acelerar(double velocidad, double distancia) {
		double aceleracionNecesaria = (velocidad - getVelocidad())/distancia;
		setAceleracion(aceleracionNecesaria);
		return true;
	}
	
	public double getVelocidadMaximaEnLugar() {
		return 0; //reconocer la velocidad maxima actual. Deberia tener un puntero al lugar actual por el que pasa. El cartel setearia esto.
	}
	
	//TODO hacer bien, desplazar el Rectangulo...
	private boolean desplazarHorizontalmente(SentidoVehiculo sentido) {
	/*	if (sentido == SentidoHorizontal.IZQUIERDA)
			getPosicion().desplazar(getAncho() * RAZON_DESPLAZAMIENTO_HORIZONTAL, 0);
		else getPosicion().desplazar(getAncho() * RAZON_DESPLAZAMIENTO_HORIZONTAL, 0);*/
		return true;
	}

	public double getAncho() {
		return ancho;
	}
	
	public double getLargo() {
		return largo;
	}
	
	protected void setSentido(SentidoVehiculo sentido) {
		this.sentido = sentido;
	}

	public SentidoVehiculo getSentido() {
		return sentido;
	}
	
	/**
	 * @brief Chequea si est� ingresando a un Transitable nuevo. Y si lo est�, gira.
	 * @return true si est� ingresando a un nuevo Transitable, false si no est� ingresando.
	 */
	private boolean checkIngresoATransitable() {
		Transitable corriente = Ciudad.getCiudad().getTransitable(getPosicion());
		if ((corriente != null) &&
			(recorrido != null) &&
			(recorrido.getSiguienteTransitable() == corriente)) {
			this.girar(corriente.getSentido());
			return true;
		}
		return false;
	}

	protected void checkChoques() {
		Ciudad ciudad = Ciudad.getCiudad();
		Iterable<Vehiculo> vehiculosEnCiudad = ciudad.getVehiculos();
		for (Vehiculo otroVehiculo : vehiculosEnCiudad) {
			if (this.getCuerpo().solapa(otroVehiculo.getCuerpo())) {
				setChocado(true);
				otroVehiculo.setChocado(true);
			}
		}
	}
	
	private void girar(SentidoTransitable sentido) {
		// TODO Auto-generated method stub
		
	}
	
	public boolean getChocado() {
		return chocado;
	}
	
	public boolean getLlegado() {
		return llegado;
	}
	
	public void checkLlegado() {
		if (cuerpo.getCentro() != null && posicionFinal != null) {
	
			Transitable zonaLlegada = Ciudad.getCiudad().getTransitable(posicionFinal);
			
			if (zonaLlegada != null)
				if (zonaLlegada.contienePosicion(cuerpo.getCentro()))
					llegado = true;
	
		}
	}
	
	protected void setChocado(boolean estadoChocado) {
		this.chocado = estadoChocado;
	}
	
	private void acelerar() {
		setVelocidad(getVelocidad() + getAceleracion() * unidadDeTiempo);
	}
		
	/**
	 * @brief Calcula la nueva posicion del vehiculo en base a su velocidad y su sentido de desplazamiento.
	 * @return NuevaPosicion = ViejaPosicion + avance*Ponderacion
	 *   Donde avance es un escalar y ViejaPosicion y Ponderacion son vectores.
	 */
	protected Vector calcularNuevaPosicion() {
		double avance = getVelocidad() * unidadDeTiempo;

		Vector nuevaPosicion = getPonderacionDeAvance(getSentido());
		nuevaPosicion.multiplicar(avance);
		
		nuevaPosicion.sumar(getPosicion());
		return nuevaPosicion;
	}
	
	private void avanzar() {
		mover(calcularNuevaPosicion());
	}
	
	protected void modificarVelocidad() {
		setVelocidad(getVelocidad() + aceleracion * unidadDeTiempo);
	}
	
	protected void setAceleracion(double aceleracion) {
		double aceleracionNormalizada= aceleracion + getFrenadoMaximo();
		
		if (aceleracionNormalizada <= 0)
			aceleracionNormalizada = getFrenadoMaximo();
		else if (aceleracion >= getFrenadoMaximo() + getAceleracionMaxima())
			aceleracionNormalizada = getAceleracionMaxima();
		else
			aceleracionNormalizada = aceleracion; 
			
		this.aceleracion = aceleracionNormalizada;
	}
	
	
	private void checkObstaculos() {
		ArrayList<Obstaculo> obstaculos = new ArrayList<Obstaculo>();
		
		if (recorrido == null) return;
		
		Transitable vista1 = recorrido.getAnteriorTransitable();
			if (vista1 != null) {
			ArrayList<Semaforo> obstaculos1 = Ciudad.getCiudad().getSemaforos(vista1);
			if (obstaculos1 != null) {
				for (Obstaculo obstaculo : obstaculos1) {
					if (!obstaculo.equals(this) && !obstaculos.contains(obstaculo))
						obstaculos.add(obstaculo);
				}
			}
		}		
		
		Transitable vista2 = recorrido.getCorrienteTransitable();
		if (vista2 != null) {
			ArrayList<Semaforo> obstaculos2 = Ciudad.getCiudad().getSemaforos(vista2);
			if (obstaculos2 != null) {
					for (Obstaculo obstaculo : obstaculos2) {
						if (!obstaculo.equals(this) && !obstaculos.contains(obstaculo))
							obstaculos.add(obstaculo);
					}	
			}
		}		
		
		Transitable vista3 = recorrido.getSiguienteTransitable();
		if (vista3 != null) {
			ArrayList<Semaforo> obstaculos3 = Ciudad.getCiudad().getSemaforos(vista3);
			if (obstaculos3 != null) {
				for (Obstaculo obstaculo : obstaculos3) {
					if (!obstaculo.equals(this) && !obstaculos.contains(obstaculo))
						obstaculos.add(obstaculo);
				}	
			}
		}
		
		
		for (Obstaculo obstaculo : obstaculos) {
			AccionRecomendada ar = obstaculo.analizarSituacion(this);
			ar.getAccion().aplicar(this);
			
		}
		
	}
	
	public double getAceleracion() {
		return aceleracion+0.01;
	}

	public double getAceleracionMaxima() {
		return aceleracionMaxima;
	}

	public double getFrenadoMaximo() {
		return frenadoMaximo;
	}

	protected void mover(Vector nuevaPosicion) {
		getCuerpo().mover(nuevaPosicion);
	}

	/** Metodos de la interfaz ITransaccion */
	@Override
	public int activar() {
		if (chocado || llegado) return 0; // si esta chocado o llegado, no se mueve.
		checkIngresoATransitable();
		checkObstaculos();
		acelerar();
		avanzar();
		checkLlegado();
		checkChoques();
		return 0;
	}
	


	@Override
	public int getPrioridad() {
		return 0;
	}	
}
